package com.xinxin.base.util;

import com.google.common.collect.ImmutableMap;
import com.xinxin.base.constant.ErrorCode;
import com.xinxin.base.entity.PlainConnectionSocketFactory;
import com.xinxin.base.entity.ProxyConfig;
import com.xinxin.base.entity.SSLConnectionSocketFactory;
import com.xinxin.base.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import java.net.Proxy;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

/**
 * @author ys
 * @date 2021/5/28
 */
@Slf4j
public class HttpClientUtil {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final ProxyConfig DIRECT = new ProxyConfig(Proxy.Type.DIRECT, "localhost", 65535);

    private final ProxyConfig proxyConfig;

    public static void main(String[] args) {
        String result;

        result = HttpClientUtil.create().doGet("http://localhost:8603/blockchain/test/member/search",
                ImmutableMap.of("account", "111222333"));
        System.out.println(result);

        result = HttpClientUtil.create().doPost("http://localhost:8603/blockchain/test/asset/ADD",
                ImmutableMap.of("assetType", "ZYZ", "amount", "3"));
        System.out.println(result);
    }

    private HttpClientUtil(ProxyConfig proxyConfig) {
        this.proxyConfig = proxyConfig;
    }

    public static HttpClientUtil create() {
        return new HttpClientUtil(DIRECT);
    }

    public static HttpClientUtil http(String hostname, Integer port) {
        return new HttpClientUtil(new ProxyConfig(Proxy.Type.HTTP, hostname, port));
    }

    public static HttpClientUtil socks(String hostname, Integer port) {
        return new HttpClientUtil(new ProxyConfig(Proxy.Type.SOCKS, hostname, port));
    }

    private CloseableHttpClient createClient() {
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new PlainConnectionSocketFactory())
                .register("https", new SSLConnectionSocketFactory(SSLContexts.createSystemDefault()))
                .build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);

        HttpHost proxy = new HttpHost(proxyConfig.getHostname(), proxyConfig.getPort());
        RequestConfig requestConfig = RequestConfig.custom()
                // .setProxy(proxy)
                .setConnectionRequestTimeout(5000)
                .setConnectTimeout(5000)
                .setSocketTimeout(60000)
                .build();

        return HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).build();
    }

    private String doRequest(String url, Callable<HttpUriRequest> request) {
        try (CloseableHttpClient client = createClient()) {
            HttpClientContext context = HttpClientContext.create();
            context.setAttribute("proxy", proxyConfig);

            String result = "";
            CloseableHttpResponse response = client.execute(request.call(), context);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                result = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
            } else {
                log.warn("http request failed: {}, statusCode: {}", url, statusCode);
            }

            response.close();
            return result;
        } catch (Exception e) {
            log.error("http request error: {}, errorMsg: {}", url, e.getMessage());
            throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR);
        }
    }

    public String doGet(String url) {
        return doGet(url, null);
    }

    public String doGet(String url, Map<String, String> params) {
        Callable<HttpUriRequest> callable = () -> {
            URIBuilder builder = new URIBuilder(url);
            if (params != null) {
                params.forEach(builder::addParameter);
            }
            return new HttpGet(builder.build());
        };

        return doRequest(url, callable);
    }

    public String doPost(String url, Map<String, String> params) {
        return doPost(url, params, null);
    }

    public String doPost(String url, Map<String, String> params, Map<String, String> headers) {
        Callable<HttpUriRequest> callable = () -> {
            HttpPost httpPost = new HttpPost(url);
            if (headers != null) {
                headers.forEach(httpPost::addHeader);
            }

            if (params != null) {
                List<NameValuePair> paramList = params.entrySet().stream()
                        .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
                        .collect(Collectors.toList());
                httpPost.setEntity(new UrlEncodedFormEntity(paramList, DEFAULT_CHARSET));
            }

            return httpPost;
        };

        return doRequest(url, callable);
    }

    public String doPostJson(String url, Map<String, String> params) {
        return doPostJson(url, params, null);
    }

    public String doPostJson(String url, Map<String, String> params, Map<String, String> headers) {
        return doPostJson(url, JsonUtil.writeValueAsString(params), headers);
    }

    public String doPostJson(String url, String json) {
        return doPostJson(url, json, null);
    }

    public String doPostJson(String url, String json, Map<String, String> headers) {
        Callable<HttpUriRequest> callable = () -> {
            HttpPost httpPost = new HttpPost(url);
            if (headers != null) {
                headers.forEach(httpPost::addHeader);
            }

            httpPost.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));

            return httpPost;
        };

        return doRequest(url, callable);
    }
}
